package org.budo.xml;

import java.io.File;
import java.io.FileReader;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.concurrent.Callable;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.budo.support.lang.util.IoUtil;
import org.budo.support.slf4j.Slf4j;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.jdom2.input.SAXBuilder;
import org.slf4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * 工具，入口 DOM/SAX/JDOM/DOM4j/XPath
 *
 * @author lmw
 */
public class Xml {
    private static final Logger log = Slf4j.getLogger();

    public static XmlObject parseString(String xml) {
        if (null == xml || xml.trim().isEmpty()) {
            log.error("#30 parseString, xml=" + xml);
            return null;
        }

        InputStream inputStream = IoUtil.toInputStream(xml);
        return parse(inputStream);
    }

    public static XmlObject parse(File file) {
        InputStream inputStream = IoUtil.toInputStream(file);
        return parse(inputStream);
    }

    public static XmlObject parse(InputStream inputStream) {
        return wrap(() -> {
            Document document = build(inputStream);
            return new DomXmlObject(document);
        });
    }

    public static XmlObject dom4j(File file) {
        return wrap(() -> {
            FileReader fileReader = new FileReader(file);
            SAXReader saxReader = new SAXReader(); // 创建一个串的字符输入流
            org.dom4j.Document document = saxReader.read(fileReader);
            return new Dom4jXmlObject(document);
        });
    }

    public static XmlObject jdom(File file) {
        return wrap(() -> {
            SAXBuilder saxBuilder = new SAXBuilder();
            org.jdom2.Document document = saxBuilder.build(file);
            return new JdomXmlObject(document.getRootElement());
        });
    }

    private static XmlObject wrap(Callable<XmlObject> callable) {
        return new XmlObjectWrapper() {
            public XmlObject getXmlObject() {
                try {
                    return callable.call();
                } catch (Throwable e) {
                    throw new RuntimeException(e);
                }
            }
        };
    }

    public static Document build(InputStream inputStream) {
        try {
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            Document document = documentBuilder.parse(inputStream);
            return document;
        } catch (Exception e) {
            log.error("#53 Xml build error, e=" + e, e);
            return null;
        }
    }

    /**
     * 根据xpath表达式和returnType从document中读取值
     *
     * @param document   被XPath解析的对象,Object类型, 可以是Document, NodeList 等
     * @param returnType javax.xml.xpath.XPathConstants枚举中的值,表示返回类型NODE/NODESET/STRING
     */
    public static Object xpath(Object xml, String xpath, QName returnType) {
        if (null == xml || null == xpath || null == returnType) {
            log.error("#79 xpath,  xml=" + xml + ", xpath=" + xpath + ", returnType=" + returnType);
            return null;
        }

        if (xml instanceof String) {
            xml = parseString((String) xml);
        }

        if (xml instanceof File) {
            xml = parse((File) xml);
        }

        if (xml instanceof InputStream) {
            xml = parse((InputStream) xml);
        }

        // 取出 org.w3c.dom.Node
        if (xml instanceof DomXmlObject) {
            xml = ((DomXmlObject) xml).getNode();
        }

        try {
            XPathExpression xpathExpression = xpathExpression(xpath);
            Object value = xpathExpression.evaluate(xml, returnType);
            return value;
        } catch (Throwable e) {
            log.error("#59 e=" + e + ",  xml=" + xml + " xpath=" + xpath + " returnType" + returnType, e);
            return null;
        }
    }

    public static XPathExpression xpathExpression(String xpath) {
        try {
            XPathFactory xpathFactory = XPathFactory.newInstance();
            XPath _xpath = xpathFactory.newXPath();
            XPathExpression xpathExpression = _xpath.compile(xpath);
            return xpathExpression;
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static String xpathString(Object xml, String xpath) {
        Object value = xpath(xml, xpath, XPathConstants.STRING);
        return null == value ? null : value.toString();
    }

    public static XmlObject xpathNode(Object xml, String xpath) {
        Object value = xpath(xml, xpath, XPathConstants.NODE);
        Node node = (Node) value;
        return new DomXmlObject(node);
    }

    public static XmlObject[] xpathNodeSet(Object xml, String xpath) {
        Object value = xpath(xml, xpath, XPathConstants.NODESET);

        NodeList nodeList = (NodeList) value;
        int len = nodeList.getLength();

        XmlObject[] xmlObjects = new XmlObject[len];
        for (int i = 0; i < len; i++) {
            Node node = nodeList.item(i);
            xmlObjects[i] = new DomXmlObject(node);
        }

        return xmlObjects;
    }

    /**
     * 格式化Xml 默认 pretty 格式
     */
    public static String format(String xml) {
        return pretty(xml);
    }

    /**
     * 格式化Xml pretty 格式
     */
    public static String pretty(String xml) {
        OutputFormat outputFormat = OutputFormat.createPrettyPrint(); // 注释：设置 xml 的输出编码
        return format(xml, outputFormat);
    }

    /**
     * 压缩Xml compact 格式
     */
    public static String compact(String xml) {
        OutputFormat outputFormat = OutputFormat.createCompactFormat(); // 注释：设置 xml 的输出编码
        return format(xml, outputFormat);
    }

    /**
     * 格式化Xml
     */
    public static String format(String xml, OutputFormat outputFormat) {
        if (null == xml || xml.trim().isEmpty() || null == outputFormat) {
            log.warn("#170 format, xml=" + xml + ", outputFormat=" + outputFormat);
            return xml;
        }

        if (!xml.trim().startsWith("<") || !xml.trim().endsWith(">")) {
            return xml;
        }

        try {
            outputFormat.setEncoding("UTF-8"); // 输出格式

            StringWriter stringWriter = new StringWriter(); // 创建输出流
            XMLWriter xmlWriter = new XMLWriter(stringWriter, outputFormat); // 输出格式化的串到目标中，执行后。格式化后的串保存在out中。

            StringReader stringReader = new StringReader(xml);
            SAXReader saxReader = new SAXReader(); // 创建一个串的字符输入流
            org.dom4j.Document document = saxReader.read(stringReader);

            xmlWriter.write(document);
            xmlWriter.close();

            return stringWriter.toString();// 返回格式化结果
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static LazyFormatXml lazyFormat(String xml) {
        return new LazyFormatXml(xml);
    }

    public static XmlObject budo(File file) {
        String xml = IoUtil.toString(file);
        return new BudoXmlObject(xml);
    }
}
